local table, next, rawset, rawget, setmetatable, ipairs, pairs, type =
      table, next, rawset, rawget, setmetatable, ipairs, pairs, type
module('apr.std.table')

--- Same as iterkeys, but return all key values in a table instead of an iterator.
-- @param t the table to pull keys from
-- @return all keys in table as a table array
function keys(t)
    return fromiter(iterkeys(t))
end

--- Return an iterator over all key values in a table
-- @param t the table to pull keys from
-- @return the key iterator function
function iterkeys(t)
    local index = nil
    return function()
        index, _ = next(t, index)
        return index
    end
end

--- Freeze an iterator into a table
-- @param iter anything iterable in a for loop
-- @return table of all values from iter
function fromiter(iter)
    local vals = {}
    for v in iter do
        table.insert(vals, v)
    end
    return vals
end

--- Extend a dictionary-like table with the values from a series of other tables or iterators
-- @param tbl the table to extend
-- @param ... a set of either tables or iterators that return key/value pairs that will update tbl in order
function extend(tbl, ...)
    for _, extent in ipairs({...}) do
        if type(extent) == 'function' then
            for k, v in extent() do
                tbl[k] = v
            end
        elseif type(extent) == 'table' then
            for k, v in pairs(extent) do
                tbl[k] = v
            end
        end
    end
end

--- A full lazily-generated table which is fully populated on first key index
-- @param gen_func a function that returns the concrete table to use
function lazytable(gen_func)
    local lazy = {}
    local lazymeta = {
        __index = function(lazy, key)
            if #lazy > 0 then return rawget(lazy, key) end
            
            extend(lazy, gen_func())
            return lazy[key]
        end
    }
    setmetatable(lazy, lazymeta)
    
    return lazy
end

function caselesstable(init_table)
    local caseless, data = {}, {}
    
    local caselessmeta = {
        __index = function(caseless, key)
            if type(key) == 'string' then
                return data[key:lower()]
            else
                return data[key]
            end
        end,
        
        __newindex = function(caseless, key, value)
            if type(key) == 'string' then
                data[key:lower()] = value
            else
                data[key] = value
            end
        end
    }
    
    setmetatable(caseless, caselessmeta)
    
    if init_table ~= nil then
        for k, v in pairs(init_table) do
            caseless[k] = v
        end
    end
    return caseless
end

function defaulttable(default_value, init_table)
    local defaultmeta = {
        __index = function(deftable, key)
            if type(default_value) == 'function' then
                deftable[key] = default_value(key, deftable)
            else
                deftable[key] = default_value
            end
            
            return deftable[key]
        end
    }
    
    init_table = init_table or {}
    setmetatable(init_table, defaultmeta)
    return init_table
end
